<?php


namespace app\lib;


use app\lib\model\chat_detail;
use app\lib\model\chat_group;
use app\lib\model\chat_group_detail;
use app\lib\model\chat_group_member;
use app\lib\model\chat_list;
use app\lib\model\user;
use app\lib\model\user_info;

/**
 * 群聊类
 * Class sock_group
 *
 * @package app\lib
 */
class sock_group extends base
{
    //双向hash
    const HASH_U2S = 'sock_group:u2s';
    const HASH_S2U = 'sock_group:s2u';

    const SET_GROUP = 'sock:gp:';
    const SET_BLOCK = 'sock:blk:';

    const MODE_CHAT  = 1;
    const MODE_GROUP = 2;

    const MSG_TYPE_TEXT = 1;
    const MSG_TYPE_IMG  = 2;

    const MSG_TYPE_HEARTBEAT = -1;
    const MSG_TYPE_NOTICE    = -2;
    const MSG_TYPE_SYSTEM    = -3;


    /**
     * 用户接入时
     *
     * @param string $sid
     *
     * @return array
     */
    public function onConnect(string $sid): array
    {
        return ['sid' => $sid, 'online' => true];
    }

    /**
     * 握手时用户端携带了协议头，用于处理鉴权
     *
     * @param string $sid
     * @param string $proto
     *
     * @return bool
     */
    public function onHandshake(string $sid, string $proto): bool
    {
        //获取token和uid
        if (false === strpos($proto, ', ')) {
            return false;
        }

        $proto_data = explode(', ', $proto);

        if (3 !== count($proto_data)) {
            return false;
        }

        [$token, $uid, $hash] = $proto_data;

        //效验 hash
        if ($hash !== hash('md5', substr($token, (int)($uid[0])))) {
            return false;
        }

        //解析对比token
        try {
            $token_data = token::new()->parse($token, 'uid');

            if ($token_data['status'] !== 0) {
                return false;
            }

            if ((int)$uid !== (int)($token_data['data']['uid'] ?? 0)) {
                return false;
            }
        } catch (\Throwable $throwable) {
            return false;
        }

        //记录redis，与后面的sid形成关系，方便转换，前端只需要用uid来进行通信即可
        $this->redis->hSet(self::HASH_U2S, $uid, $sid);
        $this->redis->hSet(self::HASH_S2U, $sid, $uid);

        return true;
    }

    /**
     * 收到消息时，处理接收方
     *
     * @param string $sid
     * @param string $msg
     *
     * @return array
     */
    public function onMessage(string $sid, string $msg): array
    {
        $msg_data = json_decode($msg, true);

        //默认文本消息
        $msg_data['type'] ??= 1;

        //心跳，直接返回
        if (self::MSG_TYPE_HEARTBEAT === $msg_data['type']) {
            $msg_data['to_sid'] = $sid;
            return $msg_data;
        }

        //聊天模式
        $msg_data['mode'] ??= self::MODE_CHAT;

        switch ($msg_data['mode']) {
            //私聊
            case self::MODE_CHAT:
                //获取接收方 SID
                $msg_data['to_sid'] = $this->findSidByUid((int)$msg_data['to_id']);

                break;

            //群聊
            default:
                $is_kf = user::new()->where(['uid', $msg_data['uid']])->read_val('is_kf');
                //todo 检查禁言设置
                //检查群状态
                $gp_status = chat_group::new()->where(['id', $msg_data['to_id']])->read_val('gp_status');
                if ($gp_status == 1) {
                    $msg_data['to_sid'] = $sid;
                    $msg_data['is_kf']  = $is_kf;
                    $msg_data['type']   = self::MSG_TYPE_NOTICE;
                    $msg_data['msg']    = '客服通道已关闭，您的消息暂时无法发出';
                    return $msg_data;
                }
                //检查成员状态
                $member_status = chat_group_member::new()->where(['member_uid', $msg_data['uid']], ['gp_id', $msg_data['to_id']])->read_val('member_status');
                if ($member_status == 1) {
                    $msg_data['to_sid'] = $sid;
                    $msg_data['is_kf']  = $is_kf;
                    $msg_data['type']   = self::MSG_TYPE_NOTICE;
                    $msg_data['msg']    = '您已被禁言，您的消息暂时无法发出';
                    return $msg_data;
                }
                $msg_data['is_kf'] = $is_kf;
                if ($is_kf == 1) {
                    $msg_data['head_img'] = 'https://kxbw.oss-cn-shanghai.aliyuncs.com/news/logo%20.png';
                    $msg_data['nickname'] = '工号' . substr($msg_data['uid'], -4);
                }

                //获取发送目标 SID
                $msg_data['to_sid'] = [];

                //获取组 key
                $group_key = self::SET_GROUP . $msg_data['to_id'];

                //组不存在，从mysql中获取所有组成员信息
                if (0 === $this->redis->sCard($group_key)) {
                    $members = chat_group_member::new()
                        ->select('member_uid')
                        ->where(['gp_id', $msg_data['to_id']])
                        ->getAll(\PDO::FETCH_COLUMN);

                    if (!empty($members)) {
                        $this->redis->sAddArray($group_key, $members);
                    }

                    $this->redis->expire($group_key, 3600);
                }

                $group_uid = $this->redis->sMembers($group_key);

                //将 UID 转为 SID
                foreach ($group_uid as $uid) {
                    $u_sid = $this->findSidByUid((int)$uid);

                    if (('' !== $u_sid) && ($u_sid != $sid)) {
                        $msg_data['to_sid'][] = $u_sid;
                    }
                }
                break;
        }
        return $msg_data;
    }

    /**
     * 准备发送的消息内容
     *
     * @param string $sid
     * @param array  $data
     * @param bool   $online
     *
     * @return array
     */
    public function onSend(string $sid, array $data, bool $online): array
    {
        //直接发送系统提示的消息，不需要入库
        if (0 > $data['type']) {
            return $data;
        }

        if ($data['mode'] == self::MODE_CHAT) {
            //私聊入库
            chat_list::new()->saveChatList($data['uid'], $data['to_id'], $data['mode'], $data['type'], $data, $online);
            chat_detail::new()->saveDetail($data['uid'], $this->genIdHex($data['mode'], $data['uid'], $data['to_id']), $data);
        } elseif ($data['mode'] == self::MODE_GROUP) {
            //群聊入库
            chat_list::new()->saveGroupChatList($data['uid'], $data['to_id'], $data['mode'], $data['type'], $data, $online);
            chat_group_detail::new()->saveDetail($data['uid'], $data['to_id'], $data);
        }

        if ($data['mode'] === self::MODE_CHAT && !$online) {
            //对方不在线
            $data = array_merge(
                $data,
                [
                    'to_sid' => &$sid,
                    'type'   => self::MSG_TYPE_NOTICE,
                    'msg'    => '对方暂时不在线，上线后会收到您的消息'
                ]
            );
        }

        //合并发送方用户信息
        $data += user_info::new()->getInfo($data['uid']);

        return $data;
    }

    /**
     * 用户断开时，清理双向hash表数据
     *
     * @param string $sid
     */
    public function onClose(string $sid): void
    {
        $uid = $this->redis->hGet(self::HASH_S2U, $sid);

        $this->redis->hDel(self::HASH_U2S, $uid);
        $this->redis->hDel(self::HASH_S2U, $sid);

        unset($sid, $uid);
    }

    /**
     * 生成唯一标识码
     *
     * @param int $mode
     * @param int $uid
     * @param int $to_uid
     *
     * @return string
     */
    public function genIdHex(int $mode, int $uid, int $to_uid): string
    {
        return $mode . ':' . ($uid > $to_uid ? dechex($uid) . ':' . dechex($to_uid) : dechex($to_uid) . ':' . dechex($uid));
    }

    /**
     * 根据 UID 获取在线 SID
     *
     * @param int $uid
     *
     * @return string
     */
    private function findSidByUid(int $uid): string
    {
        $sid = $this->redis->hGet(self::HASH_U2S, $uid);

        unset($uid);
        return is_string($sid) ? $sid : '';
    }
}